package com.neuronbit.businessflow.flow.parser;


import com.neuronbit.businessflow.intellij.model.FormattedMethodString;
import com.neuronbit.businessflow.intellij.model.SimpleType;
import com.neuronbit.businessflow.flow.project.Project;
import com.neuronbit.businessflow.intellij.model.JavaMethodTask;
import com.neuronbit.businessflow.intellij.model.Node;
import com.neuronbit.businessflow.intellij.model.SimpleType;
import com.neuronbit.businessflow.intellij.model.StatefulTask;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * The type Model parser.
 */
public class ModelParser {
    private final Project project;

    /**
     * Instantiates a new Model parser.
     *
     * @param project the project
     */
    public ModelParser(Project project) {
        this.project = project;
    }


    private void processChildren(XMLEventReader reader, JavaMethodTask task, String endTagName) throws XMLStreamException {
        while (reader.hasNext()) {
            XMLEvent event1 = getNextEvent(reader);
            if (isEndOf(event1, endTagName)) {
                return;
            }
            if (isCall(event1)) {
                StartElement element = event1.asStartElement();
                if ("callable".equals(getAttr(element, "as"))) {
                    processCallable(reader, task, element);
                } else if ("stateChecker".equals(getAttr(element, "as"))) {
                    processStateChecker(reader, (StatefulTask) task, element);
                }
            }
            if (isExceptionHandler(event1)) {
                StartElement element = event1.asStartElement();
                processExceptionHandler(reader, task, element);
            }
        }
    }

    private void processStateChecker(XMLEventReader reader, StatefulTask task, StartElement element) throws XMLStreamException {
        task.getStateChecker().setType(SimpleType.of(getAttr(element, "type")));
        task.getStateChecker().setMethod(new FormattedMethodString(getAttr(element, "method")));
        while (true) {
            XMLEvent event2 = getNextEvent(reader);
            if (isEndCall(event2)) {
                if (task instanceof StatefulTask) {
                    ((StatefulTaskNode) task).resolveStateCheckerMethod(project);
                }
                break;
            } else if (isArray(event2)) {
                int i = 0;
                do {
                    XMLEvent event3 = getNextEvent(reader);
                    if (isEndParam(event3)) {
                        continue;
                    }
                    if (isStartParam(event3)) {
                        task.getStateChecker().setParam(String.valueOf(i++), getAttr(event3.asStartElement(), "value"));
                    } else {
                        break;
                    }
                } while (true);
            }
        }
    }

    private void processCallable(XMLEventReader reader, JavaMethodTask task, StartElement element) throws XMLStreamException {
        task.getCallable().setType(SimpleType.of(getAttr(element, "type")));
        task.getCallable().setMethod(new FormattedMethodString(getAttr(element, "method")));
        while (true) {
            XMLEvent event2 = getNextEvent(reader);
            if (isEndCall(event2)) {
                if (task instanceof CallableNode) {
                    ((CallableNode) task).resolveMethod(project);
                }
                break;
            } else if (isArray(event2)) {
                int i = 0;
                do {
                    XMLEvent event3 = getNextEvent(reader);
                    if (isEndParam(event3) || isEndReturn(event3)) {
                        continue;
                    }
                    if (isStartParam(event3)) {
                        task.getCallable().setParam(String.valueOf(i++), getAttr(event3.asStartElement(), "value"));
                    } else if (isStartReturn(event3)) {
                        task.getCallable().setReturn(getAttr(event3.asStartElement(), "name"), getAttr(event3.asStartElement(), "value"));
                    } else {
                        break;
                    }
                } while (true);
            }
        }
    }

    private void processExceptionHandler(XMLEventReader reader, JavaMethodTask task, StartElement element) throws XMLStreamException {
            task.getExceptionHandler().setType(SimpleType.of(getAttr(element, "type")));
            task.getExceptionHandler().setMethod(new FormattedMethodString(getAttr(element, "method")));
            while (true) {
                XMLEvent event2 = getNextEvent(reader);
                if (isEndExceptionHandler(event2)) {
//                    if (task instanceof CallableNode) {
//                        ((CallableNode) task).resolveMethod(classFinder);
//                    }
                    break;
                } else if (isArray(event2)) {
                    int i = 0;
                    do {
                        XMLEvent event3 = getNextEvent(reader);
                        if (isEndParam(event3) || isEndReturn(event3)) {
                            continue;
                        }
                        if (isStartParam(event3)) {
                            task.getExceptionHandler().setParam(String.valueOf(i++), getAttr(event3.asStartElement(), "value"));
                        } else if (isStartReturn(event3)) {
                            task.getExceptionHandler().setReturn(getAttr(event3.asStartElement(), "name"), getAttr(event3.asStartElement(), "value"));
                        } else {
                            break;
                        }
                    } while (true);
                }
            }
    }


    private boolean isExceptionHandler(XMLEvent event) {
        return event != null && event.isStartElement() && getName(event).equalsIgnoreCase("ExceptionHandler");
    }

    private boolean isEndExceptionHandler(XMLEvent event) {
        return event != null && event.isEndElement() && getName(event).equalsIgnoreCase("ExceptionHandler");
    }

    private boolean isCall(XMLEvent event) {
        return event != null && event.isStartElement() && getName(event).equalsIgnoreCase("Call");
    }

    private boolean isEndOf(XMLEvent event, String tagName) {
        return event != null && event.isEndElement() && getName(event).equalsIgnoreCase(tagName);
    }

    private boolean isEndCall(XMLEvent event) {
        return event != null && event.isEndElement() && getName(event).equalsIgnoreCase("Call");
    }

    private boolean isStartParam(XMLEvent event) {
        return event != null && event.isStartElement() && getName(event).equalsIgnoreCase("Param");
    }

    private boolean isEndParam(XMLEvent event) {
        return event != null && event.isEndElement() && getName(event).equalsIgnoreCase("Param");
    }

    private boolean isStartReturn(XMLEvent event) {
        return event != null && event.isStartElement() && getName(event).equalsIgnoreCase("Return");
    }

    private boolean isEndReturn(XMLEvent event) {
        return event != null && event.isEndElement() && getName(event).equalsIgnoreCase("Return");
    }

    private boolean isArray(XMLEvent event) {
        return event != null && event.isStartElement() && getName(event).equalsIgnoreCase("Array");
    }

    private String getName(XMLEvent event1) {
        if (event1.isStartElement()) {
            return event1.asStartElement().getName().getLocalPart();
        } else if (event1.isEndElement()) {
            return event1.asEndElement().getName().getLocalPart();
        } else {
            return event1.asCharacters().getData();
        }
    }

    private XMLEvent getNextEvent(XMLEventReader reader) throws XMLStreamException {
        while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            if (!event.isCharacters()) {
                return event;
            }
        }
        return null;
    }

    private String getAttr(StartElement startElement, String attrName) {
        Attribute idAttr = startElement.getAttributeByName(new QName(attrName));
        if (null != idAttr) {
            return idAttr.getValue();
        } else {
            return "";
        }
    }

    /**
     * Parse flow node.
     *
     * @param schema the schema
     * @return flow node
     * @throws XMLStreamException the xml stream exception
     */
    public FlowNode parse(String schema) throws XMLStreamException {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLEventReader reader = factory.createXMLEventReader(new StringReader(schema));
        String id = "";
        FlowNode flow = null;
        ExceptionHandlerNode exceptionHandler = null;
        List<EdgeNode> edges = new ArrayList<>();
        List<Node> nodes = new ArrayList<>();
        while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            if (event.isStartElement()) {
                StartElement startElement = event.asStartElement();
                String tagName = startElement.getName().getLocalPart();
                switch (tagName) {
                    case "mxCell":
                        id = getAttr(startElement, "id");
                        String isEdge = getAttr(startElement, "edge");
                        if ("1".equals(isEdge)) {
                            EdgeNode edge = new EdgeNode(id, "");
                            edge.setValue(getAttr(startElement, "value"));
                            edge.setSource(getAttr(startElement, "source"));
                            edge.setTarget(getAttr(startElement, "target"));
                            edges.add(edge);
                        }
                        break;
                    case "Flow":
                        flow = new FlowNode(id, getAttr(startElement, "name"));
                        flow.setDescription(getAttr(startElement, "description"));
                        String reenterable = getAttr(startElement, "reenterable");
                        flow.setReenterable("1".equals(reenterable));
                        processChildren(reader, flow, "Flow");
                        break;
                    case "AutoTask":
                        AutoTaskNode autoTask = new AutoTaskNode(id, getAttr(startElement, "name"));
                        autoTask.setDescription(getAttr(startElement, "description"));
                        processChildren(reader, autoTask, "AutoTask");
                        nodes.add(autoTask);
                        break;
                    case "StatefulTask":
                        StatefulTaskNode statefulTaskNode = new StatefulTaskNode(id, getAttr(startElement, "name"));
                        statefulTaskNode.setDescription(getAttr(startElement, "description"));
                        processChildren(reader, statefulTaskNode, "StatefulTask");
                        nodes.add(statefulTaskNode);
                        break;
                    case "SwitchTask":
                        SwitchTaskNode switchTask = new SwitchTaskNode(id, getAttr(startElement, "name"));
                        switchTask.setDescription(getAttr(startElement, "description"));
                        processChildren(reader, switchTask, "SwitchTask");
                        nodes.add(switchTask);
                        break;
                    case "Decision":
                        DecisionNode decision = new DecisionNode(id, getAttr(startElement, "name"));
                        decision.setDescription(getAttr(startElement, "description"));
                        processChildren(reader, decision, "Decision");
                        nodes.add(decision);
                        break;
                    case "Start":
                        StartNode start = new StartNode(id, getAttr(startElement, "name"));
                        start.setDescription(getAttr(startElement, "description"));
                        nodes.add(start);
                        break;
                    case "End":
                        EndNode end = new EndNode(id, getAttr(startElement, "name"));
                        end.setDescription(getAttr(startElement, "description"));
                        nodes.add(end);
                        break;
                    case "Fork":
                        ForkNode fork = new ForkNode(id, getAttr(startElement, "name"));
                        fork.setDescription(getAttr(startElement, "description"));
                        nodes.add(fork);
                        break;
                    case "Join":
                        JoinNode join = new JoinNode(id, getAttr(startElement, "name"));
                        join.setDescription(getAttr(startElement, "description"));
                        nodes.add(join);
                        break;
                }
            }
        }
        if (null != flow) {
            flow.putNode(nodes);
            flow.putEdge(edges);
        }
        return flow;
    }


}
